/* 
 * File:   mmap_sort_v_1.h
 * Author: stas
 *
 * Created on 12 Апрель 2013 г., 15:53
 */
#ifndef MMAP_SORT_V_1_H
#define	MMAP_SORT_V_1_H

//std libs
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>

//для mmap
#include <unistd.h> 
#include <sys/mman.h>

//для open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#include "logger.h"

#define FILE_MODE_OPEN 1
#define FILE_MODE_WRITE 2

#define COUNT_MMAP_PAGES_IN_FILE_PART 1

using namespace std;

class FileReadIterator;
class File;
class FileRead;

class File{
    friend class FileReadIterator;
public:
    char *path;
    int fd;     //file descriptor
    
    int length;
    int part_length;
    
    char *mapper;      //указатель на маппируемую область памяти
    static int MMAP_PAGE_SIZE;
    
    File():part_length(MMAP_PAGE_SIZE){}
    virtual void open_file()=0;
    void close_file(){
        close(fd);
    };
    void unmap(){
        if(mapper != NULL)
            munmap(mapper, part_length);
    }
    
    void set_part_length(int l){
        part_length = l;
    }
};

template <class A>
class FileIterator{
public:
    A *file;
    int part_index;
    inline int get_map_part_size();
    void map();
    inline int get_map_offset();
public:
    FileIterator(A *file):part_index(0), file(file){}
    FileIterator(A *file, int index):part_index(index), file(file){}
    
    FileIterator(const FileIterator<A> &i){
        file = i.file;
        part_index = i.part_index;
    }
    
    bool operator ==(const FileIterator<A> &i){
        return (i.part_index == part_index);
    }
    bool operator !=(const FileIterator<A> &i){
        return (not (*this == i));
    }
    
    FileIterator operator++();
    FileIterator operator++(int i);
    
    FileIterator operator--(int i);
    
    A *operator *();
    
    A *operator->();
};


template<class A>int FileIterator<A>::get_map_part_size(){
    int part_length = file->length - part_index*(file->MMAP_PAGE_SIZE);

    if(part_length>file->MMAP_PAGE_SIZE)
        part_length = file->MMAP_PAGE_SIZE;

    return part_length;
}

template<class A>int FileIterator<A>::get_map_offset(){
    return file->MMAP_PAGE_SIZE*part_index;
}


template<class A>A *FileIterator<A>::operator *(){
    if(file != NULL)
        file->unmap();
    map();
    return file;
}

template<class A> A *FileIterator<A>::operator->(){
    return *(*this);
}

template<class A>FileIterator<A> FileIterator<A>::operator ++(int i){
    part_index++;
    file->set_part_length(this->get_map_part_size());
    return *this;
}

template<class A>FileIterator<A> FileIterator<A>::operator --(int i){
    part_index--;
    file->set_part_length(this->get_map_part_size());
    return *this;
}


class FileRead: public File{
public:
    friend class FileReadIterator;
    void open_file();
    typedef FileReadIterator iterator;
    FileRead(char *path){
        this->path = path;
        this->mapper = NULL;
        this->fd = 0;
        open_file();
    }
    
    char *read_file();
    
    FileReadIterator begin();
    
    FileReadIterator end();
};

void FileRead::open_file(){
    fd = open(path, O_RDONLY);
    if(-1 == fd){
        Log::Instanse().log("ERROR", string("File ")+string(path)+
        string(" doesn`t exists. Exiting."));
        exit(-1);
    }
    length = lseek(fd, 0, SEEK_END);
}

char *FileRead::read_file(){
    return mapper;
}

class FileReadIterator:public FileIterator<FileRead>{
protected:
public:
    FileReadIterator(FileRead *file):FileIterator<FileRead>(file){};
    FileReadIterator(FileRead *file, int l):FileIterator<FileRead>(file, l){};
    
};

template<>void FileIterator<FileRead>::map(){
    int offset = get_map_offset();

    int part_length = file->part_length;

    file->mapper = (char *)mmap(file->mapper, part_length, PROT_READ, MAP_SHARED,
                    file->fd, offset);


    if(MAP_FAILED == file->mapper){
        Log::Instanse().log("ERROR", "MAP_FAILED");
        exit(-1);
    }
    
    file->part_length = part_length;
}

FileReadIterator FileRead::begin(){
    return FileReadIterator(this);
}

FileReadIterator FileRead::end(){
    int l;
    if(this->length%this->MMAP_PAGE_SIZE)
        l = this->length/this->MMAP_PAGE_SIZE;
    else
        l = int(this->length/this->MMAP_PAGE_SIZE);
    return FileReadIterator(this, l+1);
}

int File::MMAP_PAGE_SIZE = sysconf(_SC_PAGE_SIZE)*COUNT_MMAP_PAGES_IN_FILE_PART;

class FileWriteIterator;

class FileWrite:public File{
    friend class FileWriteIterator;
private:
    void open_file();
public:
    typedef FileWriteIterator iterator;
    FileWrite(char *path){
        this->path = path;
        this->length = 0;
        open_file();
    }
    
    void write_file(const char *data);
    
    FileWriteIterator begin();
    FileWriteIterator end();
    
    void expend(int size);
};

void FileWrite::expend(int size){
    this->length += size;
    ftruncate(this->fd, this->length);
}

void FileWrite::open_file(){
    fd = open(path, O_RDWR|O_CREAT, S_IRWXU);
}

void FileWrite::write_file(const char *data){
    memcpy(mapper, data, this->part_length);
}

class FileWriteIterator: public FileIterator<FileWrite>{
public:
    FileWriteIterator(FileWrite *file):FileIterator(file){};
    FileWriteIterator(FileWrite *file, int l):FileIterator(file, l){};
    
};

template<>void FileIterator<FileWrite>::map(){
    int offset = get_map_offset();

    int part_length = file->part_length;

    file->mapper = (char *)mmap(NULL, part_length, PROT_WRITE, MAP_SHARED,
                    file->fd, offset);

    if(MAP_FAILED == file->mapper){
        Log::Instanse().log("ERROR", "MAP_FAILED");
        exit(-1);
    }
    
    file->part_length = part_length;
}

FileWriteIterator FileWrite::begin(){
    return FileWriteIterator(this);
}

FileWriteIterator FileWrite::end(){
    int l;
    if(this->length%this->MMAP_PAGE_SIZE)
        l = this->length/this->MMAP_PAGE_SIZE;
    else
        l = int(this->length/this->MMAP_PAGE_SIZE);
    return FileWriteIterator(this, l+1);
}

class ParsePart{
public:
    string part;
    string prefix;
    string postfix;
    
    vector<int> parsed;
    void parse();
public:
    ParsePart(string &data):part(data){parse();}
    
    string get_prefix(){return prefix;}
    string get_postfix(){return postfix;}
    vector<int> &get_parsed(){return parsed;}
};

int parse_int(string &data){
    int out = atoi(data.c_str());
    if(out == 0 and data!=string("0")){
        Log::Instanse().log("ERROR", "Incorrect input file(some nonintegers symbols). Exiting");
        exit(-1);
    }
    return out;
}

void ParsePart::parse(){
    vector<int> out;
    string temp;
    
    bool IS_ON_PREFIX = true;
    
    for(string::const_iterator i=part.begin(); i!=part.end(); i++){
        if(*i == ','){
            if(IS_ON_PREFIX){
                prefix = temp;
                IS_ON_PREFIX = false;
            }else{
                out.push_back(parse_int(temp));
            }
            temp.clear();
        }else if(*i == ' ' or *i == '\t' or *i == '\n'){
        }else{
            temp += *i;
        }
    }
    postfix = temp;
    parsed = out;
}

vector<int> sort_vector(FileRead &file){
    string postfix;
    vector<int> out;
    for(FileRead::iterator i = file.begin(); i!= file.end(); i++){
        string data(i->read_file(), file.part_length);
        
        ParsePart p_class(data);
        
        string summ = postfix+p_class.prefix;
        out.push_back(parse_int(summ));
        
        vector<int> parsed_vectors = p_class.get_parsed();
        
        for(vector<int>::iterator j = parsed_vectors.begin(); j!=parsed_vectors.end();
                j++){
            out.push_back(*j);
        }
        
        postfix = p_class.postfix;
    }
    out.push_back(parse_int(postfix));
    sort(out.begin(), out.end());
    return out;
}

void main_alg(vector<int> vec, FileWrite file){
    string out;
    ostringstream s(out);
    for(vector<int>::iterator i = vec.begin(); i!=--vec.end(); i++){
        s<<*i<<",";
    }
    s<<*(--vec.end());
    s.flush();
    out = s.str();
    
    file.expend(out.size());
    
    for(FileWrite::iterator i = file.begin(); i!= file.end(); i++){
        
        string substr = out.substr(i.part_index*file.MMAP_PAGE_SIZE, 
                file.part_length).c_str();
        
        i->write_file(substr.c_str());
    }
}

#endif	/* MMAP_SORT_V_1_H */

